/*
 * User: avishay
 * Date: 17/07/2008 
 *
 * Copyright (2005) IDI. All rights reserved.
 * This software is a proprietary information of Israeli Direct Insurance.
 * Created by IntelliJ IDEA. 
 */
package com.idi.remote.assistant;

import com.idi.maven.plugins.PluginUtils;
import org.apache.commons.net.ftp.FTP;
    import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPSClient;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import static java.lang.String.format;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.security.NoSuchAlgorithmException;

/**
 *
 */
public class RemoteAccessFacade {

    private final Map<String, FTPClient> ftpClients = new HashMap<String, FTPClient>(2);
    private final Map<String, FTPSClient> ftpsClients = new HashMap<String, FTPSClient>(2);
    private final Map<String, SshExecClient> sshExecClients = new HashMap<String, SshExecClient>(2);

    private Log log;
    private RemoteAccessProvider provider;

    public RemoteAccessFacade(Log log, RemoteAccessProvider conf) {
        this.log = log;
        this.provider = conf;
    }

    public FTPClient getFtpClient(String host) throws MojoExecutionException {
        if (!ftpClients.containsKey(host)) {
            final FTPClient ftpClient = new FTPClient();
            try {
                ftpClient.setRemoteVerificationEnabled(false);
                ftpClient.connect(host);
                final boolean success = ftpClient.login(provider.getDeployUser(), provider.getDeployUserPassword());
                if (!success) {
                    throw new MojoExecutionException(format("Could not connect to %s. please verify user and passord", host));
                }
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            } catch (IOException e) {
                throw new MojoExecutionException(format("Could not connect to %s. please verify user and passord", host), e);
            }
            ftpClients.put(host, ftpClient);
        }
        return ftpClients.get(host);
    }

    public void sFtpUpload(String host) throws MojoExecutionException {

    }

    public SshExecClient getSshExec(String host) {
        if (!sshExecClients.containsKey(host)) {
            sshExecClients.put(host, new SshExecClient(host, provider));
        }
        return sshExecClients.get(host);
    }

    public void executeRemoteCommand(String command, String host, boolean... optional) throws MojoExecutionException {
        boolean shouldLog = optional.length == 0 || (optional.length > 0 && optional[0]);
        if (shouldLog) {
            log.info(format("Executing command [%s] on host [%s]", command, host));
        }
        getSshExec(host).execute(command);
    }

    public void uploadFile(String host, String remoteTargetDirectory, File file, boolean... optional) throws IOException, MojoExecutionException {
        boolean shouldLog = optional.length == 0 || (optional.length > 0 && optional[0]);
        if (shouldLog) {
            log.info(format("Uploading file [%s] to directory [%s] on host [%s] ", file.getName(), remoteTargetDirectory, host));
        }
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            String targetPath = remoteTargetDirectory + "/" + file.getName();
            final FTPClient client = getFtpClient(host);
            boolean successful = client.storeFile(targetPath, fis);
            fis.close();
            if (!successful) {
                throw new MojoExecutionException(MessageFormat.format("Failed uploading file {0} to ftp://{1}:{2}\nReason: {3}", file, host, targetPath, client.getReplyString()));
            }
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
    }

    public boolean fetchFile(String host, String remoteFileName, final File targetLocalFile, boolean... optional) throws MojoExecutionException {
        boolean shouldLog = optional.length == 0 || (optional.length > 0 && optional[0]);

        if (shouldLog) {
            log.info(MessageFormat.format("Downloading {0} from {1} to {2}", remoteFileName, host, targetLocalFile));
        }

        boolean successful;
        FileOutputStream fos = null;
        try {
            FTPClient ftp = getFtpClient(host);
            fos = new FileOutputStream(targetLocalFile);
            successful = ftp.retrieveFile(remoteFileName, fos);
            fos.close();
        } catch (IOException e) {
            throw new MojoExecutionException(MessageFormat.format("Failed fetching file {0} from {1} to {2}", remoteFileName, host, targetLocalFile), e);
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                throw new MojoExecutionException(MessageFormat.format("Failed closing stream to file {0}", targetLocalFile), e);
            }
        }
        return successful;
    }

    public void fetchRemoteDirViaZip(String serverHost, String remoteDirectory, File targetLocalDirectory) throws MojoExecutionException {
        if (!targetLocalDirectory.isDirectory()) {
            throw new RuntimeException(format("%s should be a directory", targetLocalDirectory));
        }

        if (targetLocalDirectory.list().length != 0) {
            log.warn(format("target directory %s is not empty. is it ok?", targetLocalDirectory));
        }

        log.info(format("fetching remote directory %s from %s via zip into local directory %s", remoteDirectory, serverHost, targetLocalDirectory));

        final String tempFileName = "fetchRemoteDirViaZip" + System.nanoTime() + ".zip";
        String remoteWorkdir = remoteDirectory + "/../";
        final String remoteFullFileName = remoteWorkdir + tempFileName;
        File tempLocalFile = null;
        try {
            //zipping remote client dir in remote server
            executeRemoteCommand(
                    format("cd %s ; ls *.jar | xargs zip -q -r %s/%s", remoteDirectory, remoteWorkdir, tempFileName), serverHost, false);

            //fetching file from remote server
            tempLocalFile = new File(targetLocalDirectory, tempFileName);
            if (fetchFile(serverHost, remoteFullFileName, tempLocalFile, false)) {

                //extracting localChecksum zip file to checksum directory
                PluginUtils.extractZipFile(tempLocalFile, targetLocalDirectory);
            } else {
                log.warn(String.format("Failed receiving file %s from %s , This might happend on first deployment when there are no jars on the server yet", tempLocalFile, remoteFullFileName));
            }
        } finally {
            //remove local
            if (tempLocalFile != null) {
                tempLocalFile.delete();
            }
            //remove remote
            executeRemoteCommand("rm " + remoteFullFileName, serverHost, false);
        }
    }

    public void uploadFilesToRemoteDirViaZip(List<String> hosts, String uploadDir, Set<File> filesToUpload, File localWorkdir) throws IOException, MojoExecutionException {
        File localArchive = null;
        String tempName = System.nanoTime() + ".zip";
        String remoteWorkdir = uploadDir + "/../";
        String remoteArchiveZipFile = remoteWorkdir + tempName;
        String localArchiveZipFile = new File(localWorkdir, tempName).getAbsolutePath();

        log.info(format("uploading %d files to %s on %s, using temp zip %s", filesToUpload.size(), uploadDir, hosts.toString(), localArchiveZipFile));

        try {
            localArchive = PluginUtils.createArchive(localArchiveZipFile, filesToUpload);

            for (final String host : hosts) {
                //noinspection NestedTryStatement
                try {
                    //sending archive to server.
                    uploadFile(host, remoteWorkdir, localArchive, false);
                    //unzipping archive in server
                    executeRemoteCommand(MessageFormat.format("unzip -q -o {0} -d {1}", remoteArchiveZipFile, uploadDir), host, false);
                } finally {
                    //cleanup temporary archive from remote server
                    executeRemoteCommand(MessageFormat.format("rm {0}", remoteArchiveZipFile), host, false);
                }
            }
        } finally {
            //cleanup temporary archive from local directory
            if (localArchive != null) {
                localArchive.deleteOnExit();
            }
        }
    }

    public void closeAll() throws MojoExecutionException {
        for (final FTPClient ftpClient : ftpClients.values()) {
            try {
                ftpClient.logout();
                ftpClient.disconnect();
            } catch (IOException e) {
                log.error(MessageFormat.format("Failed closing FTP connection to {0}", ftpClient.getRemoteAddress()));
            }
        }
        ftpClients.clear();
    }


    public void setLog(Log log) {
        this.log = log;
    }
}